package com.zy.convert.util;

import com.zy.convert.constant.ConvertConstant;
import com.zy.convert.exception.ConvertParamException;
import org.apache.commons.lang3.StringUtils;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.lang.reflect.*;
import java.util.function.Consumer;
import java.util.logging.Logger;

/**
 * @author hassan
 * @date 2020-02-24 17:30
 * 反射工具类
 */
public class ReflectHelper {
    private final static Logger logger = Logger.getLogger(ReflectHelper.class.getName());
    private final static String SETTER_PREFIX = "set";
    private final static String GETTER_PREFIX = "get";
    // 不做缓存
//    private final static Map<String, Method> cacheMethod = new HashMap<>();
//    private final static Map<String, Field> cacheField = new HashMap<>();

    /**
     * 获取父类泛型类
     */
    public static Class<?> getSuperOneGeneric(Class<?> sourceClass) {
        Class<?>[] superGeneric = getSuperGeneric(sourceClass);
        return superGeneric == null ? null : superGeneric[0];
    }

    public static Class<?>[] getSuperGeneric(Class<?> sourceClass) {
        // Type是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。
        Type type = sourceClass.getGenericSuperclass();
        if (type == null) {
            return null;
        }
        // ParameterizedType参数化类型，即泛型
        if (ParameterizedType.class.isAssignableFrom(type.getClass())) {
            ParameterizedType p = (ParameterizedType) type;
            // getActualTypeArguments获取参数化类型的数组，泛型可能有多个
            Type[] actualTypeArguments = p.getActualTypeArguments();
            Class<?>[] types = new Class[actualTypeArguments.length];
            for (int i = 0; i < actualTypeArguments.length; i++) {
                Type actualTypeArgument = actualTypeArguments[i];
                if (ParameterizedTypeImpl.class.isAssignableFrom(actualTypeArgument.getClass())){
                    types[i]=((ParameterizedTypeImpl) actualTypeArgument).getRawType();
                }else if (Class.class.isAssignableFrom(actualTypeArgument.getClass())) {
                    types[i] = (Class<?>) actualTypeArgument;
                }
            }
            return types;
        }
        return null;
    }

    /**
     * 属性复制，根据targetClass创建对象，并通过getter和setter将source中的值复制到新创建的对象中
     *
     * @param source      源对象
     * @param targetClass 目标对象类型
     * @date 2020/3/16 16:29
     * 通过getter setter效率不高,使用JSONObject代替。{@link com.alibaba.fastjson.JSONObject}
     */
    @Deprecated
    public static <T> T copyFieldValue(Object source, Class<T> targetClass) {
        if (source == null) {
            return null;
        }
        try {
            T target = newInstance(targetClass);
            processFields(source.getClass(), field -> {
                String fieldName = field.getName();
                Object object = getProperty(source, fieldName);
                setProperty(target, fieldName, object);
            });
            return target;
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            logger.info(e.getClass().getSimpleName() + " : " + e.getMessage());
        }
        return null;
    }


    /**
     * @param target    需要设置属性的目标对象
     * @param fieldName 属性名
     * @param arg       属性值
     */
    public static boolean setProperty(Object target, String fieldName, Object arg) {
        if (target == null || StringUtils.isEmpty(fieldName)) {
            return false;
        }
        Class<?> targetClass = target.getClass();
        try {
            Method setter = getSetter(targetClass, fieldName);
            if (setter == null) {
                return false;
            }
            setter.setAccessible(true);
            setter.invoke(target, arg);
            return true;
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | NoSuchFieldException | ConvertParamException e) {
            logger.info(e.getClass().getSimpleName() + " : " + e.getMessage());
        }
        return false;
    }

    /**
     * @param source    获取属性的目标对象
     * @param fieldName 属性名
     */
    public static Object getProperty(Object source, String fieldName) {
        if (source == null || StringUtils.isEmpty(fieldName)) {
            return null;
        }
        Class<?> sourceClass = source.getClass();
        try {
            Method getter = getGetter(sourceClass, fieldName);
            if (getter == null) {
                return null;
            }
            getter.setAccessible(true);
            return getter.invoke(source);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | NoSuchFieldException | ConvertParamException e) {
            logger.info(e.getClass().getSimpleName() + " : " + e.getMessage());
        }
        return null;
    }


    public static void processFields(Class<?> targetClass, Consumer<Field> consumer) {
        Class<?> currentClass = targetClass;
        while (!currentClass.equals(ConvertConstant.CLASS_OBJECT)) {
            Field[] fields = currentClass.getDeclaredFields();
            for (Field field : fields) {
                consumer.accept(field);
            }
            currentClass = currentClass.getSuperclass();
        }
    }

    public static <T> T newInstance(Class<T> targetClass) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        if (targetClass == null) {
            return null;
        }
        Constructor<T> constructor = targetClass.getConstructor();
        constructor.setAccessible(true);
        return constructor.newInstance();
    }

    public static Method getSetter(Class<?> targetClass, String fieldName) throws NoSuchMethodException, NoSuchFieldException, ConvertParamException {
        return doGetMethod(targetClass, fieldName, true);
    }

    public static Method getGetter(Class<?> sourceClass, String fieldName) throws NoSuchMethodException, NoSuchFieldException, ConvertParamException {
        return doGetMethod(sourceClass, fieldName, false);
    }

    private static Method doGetMethod(Class<?> from, String fieldName, boolean setter) throws NoSuchMethodException, NoSuchFieldException, ConvertParamException {
        if (StringUtils.isEmpty(fieldName)) {
            throw new ConvertParamException("fieldName null");
        }
        String methodName = setter ? field2setter(fieldName) : field2getter(fieldName);
        Field field = getField(from, fieldName);
        Class<?>[] fieldType = setter ? new Class<?>[]{field.getType()} : new Class<?>[]{};
        return getMethod(from, methodName, fieldType);
    }

    public static Field getField(Class<?> sourceClass, String fieldName) throws NoSuchFieldException, ConvertParamException {
        if (StringUtils.isEmpty(fieldName)) {
            throw new ConvertParamException("fieldName null");
        }
        /* 做缓存
        Class<?> currentClass = sourceClass;
        String cacheKey = sourceClass.getName() + "." + fieldName;
        // 双重验证
        if (!cacheField.containsKey(cacheKey)) {
            synchronized (ReflectUtil.class) {
                if (!cacheField.containsKey(cacheKey)) {
                    while (!currentClass.equals(ConvertConstant.CLASS_OBJECT)) {
                        try {
                            Field field = currentClass.getDeclaredField(fieldName);
                            cacheField.put(cacheKey, field);
                            break;
                        } catch (NoSuchFieldException ignored) {}
                        currentClass = currentClass.getSuperclass();
                    }
                }
            }
        }
        Field field=cacheField.get(cacheKey);
        if (field!=null) {
            return field;
        }
        */
        // 不做缓存
        Class<?> currentClass = sourceClass;
        while (!currentClass.equals(ConvertConstant.CLASS_OBJECT)) {
            try {
                return currentClass.getDeclaredField(fieldName);
            } catch (NoSuchFieldException ignored) {
            }
            currentClass = currentClass.getSuperclass();
        }
        throw new NoSuchFieldException(sourceClass.getName() + " has no field [" + fieldName + "]");
    }

    private static Method getMethod(Class<?> sourceClass, String methodName, Class<?>... argTypes) throws NoSuchMethodException, ConvertParamException {
        if (StringUtils.isEmpty(methodName)) {
            throw new ConvertParamException("methodName null");
        }

        Class<?> currentClass = sourceClass;
        StringBuilder suffix = new StringBuilder();
        for (int i = 0; i < argTypes.length; i++) {
            Class<?> argType = argTypes[i];
            if (i > 0) {
                suffix.append(",");
            }
            suffix.append(argType.getName());
        }
        String cacheKey = sourceClass.getName() + "." + methodName + "[" + suffix + "]";
        /* 做缓存
        if (!cacheMethod.containsKey(cacheKey)){
            synchronized (ReflectUtil.class){
                if (!cacheMethod.containsKey(cacheKey)){
                    while (!currentClass.equals(ConvertConstant.CLASS_OBJECT)) {
                        try {
                            Method method = currentClass.getDeclaredMethod(methodName, argTypes);
                            cacheMethod.put(cacheKey, method);
                        } catch (NoSuchMethodException ignored) {}
                        currentClass = currentClass.getSuperclass();
                    }
                }
            }
        }
        Method method=cacheMethod.get(cacheKey);
        if (method!=null) {
            return method;
        }
        */

        // 不做缓存
        while (!currentClass.equals(ConvertConstant.CLASS_OBJECT)) {
            try {
                return currentClass.getDeclaredMethod(methodName, argTypes);
            } catch (NoSuchMethodException ignored) {
            }
            currentClass = currentClass.getSuperclass();
        }
        throw new NoSuchMethodException(sourceClass.getName() + " has no method " + cacheKey);
    }

    public static String field2setter(String fieldName) {
        if (StringUtils.isEmpty(fieldName)) {
            return "";
        }
        return SETTER_PREFIX + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    }

    public static String field2getter(String fieldName) {
        if (StringUtils.isEmpty(fieldName)) {
            return "";
        }
        return GETTER_PREFIX + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    }


}
